Verilog Hardware Description Language

In the design flow introduction, we have briefly introducted the purpose of Hardware Description Languages, as a mean to describe a Hierarchical Digital System, simulate it and automate is translation to a physical logic gate representation.

In this part of the lecture, we will introduce the Verilog language, and describe the required constructs to build a system. It is recommended to come back to this page during the lab work, and use it as a reference.

There are a lot of resources online to learn more about Verilog, some may present some language features used in a different way than in this lecture. We will focus here on one syntax variation to write Hardware, which is the most widespread.

At the head of this page you will see the Slides from last year. They are available for Download here

Standard Programming languages and HDL

To start with this part of the lecture, we may want to clarify the difference between the Verilog language, and the common programming languages we know about like C.

A programming language is used to define a determinist set of instructions executed by a matching programmable machine, like a processor. Therefore, the program in its textual representation represents linear set of ordered instructions:

1
2
3
4
5
6
int a = 0; // 1 -> Memory Allocation
int b = 2; // 2 -> Memory Allocation
int c = 3; // 3 -> Memory Allocation

c = a + b; // 4 -> Add
b = c + a; // 5 -> Add

On the contrary, an Hardware Description Language, is as its name hints a logic description language, not a programming language. It means that value assignments are considered concurrent as long a they are no interdependend:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
wire a = 0; // nothing special here
reg b = 1; // nothing special here
reg c = 1; // nothing special here
wire d = 1;  // nothing special here

always @(posedge clk) begin // Updates on clock edge

   // c and b are set by two logic functions
   // they are concurrently updated on clock edge
   c <= a & b; //  Just a AND b
   b <= d | a; //  Just d AND a

end

This is very important to keep this basic fact in mind, especially for the lab work:

The Verilog code is NOT a sequential instruction description, but really a logic circuit description

Register Transfer Level (RTL)

We have introduced before the concept of Pipeline Stage in Synchronous logic, by describing how memory elements like Flip Flops are updated during a clock period by sampling the output of a logic cloud.

The memory element can be generically refered to as a register. We can thus say that a Pipeline Stage transfers the value of a register to another one.

This is why the Hardware Description is said to be describing hardware at the Register Transfer Level.

Synthesisable and Simulation Subsets

Before starting a hardware modelisation, we also need to be clear about two kind of modelisation constructs:

  • The Synthesisable subset, which is used to describe the actual hardware (example: “=” is synthesisable, it assigns the result of a logic gate to a wire)
  • The Non Synthesisable subset, which is used to write Simulation code (example: “fork” can declare a Thread in the simulator)

This is a very important point, because typically, the Simulator allows us to write the verification code using the hardware description language, but by allowing descriptions which make no sense for the hardware;

Let’s have a look at this very simple example:

1
2
3
4
5
6
7
8
9
reg testValue;

inital begin // Beginning of the simulation

   testValue = 0; // Set to 0

   #200 testValue = 1; // Set to 1 after "200" time units

end

The presented code is written in the Verilog language, but it makes no sense to describe a hardware:

  • The “initial begin” defines a block executed at the begining of the simulator time.

Obviously, we could not translate this contruct into a physical logic function…because when is “beginning” whitout a physical definition, like a Power on detection, a clock starting etc…

  • The “#200” construct waits for 200 unit times.

This construct could not be translated to hardware either. From where would we know how much time is “1” unit? How would we know a time unit has passed?

Hierarchy Definition

Following the introductory part of this lecture, we can start by describing how to create a Hierarchy in Verilog.

Hierarchy Container Definition

A Hiearchical container is called a Module , and requires a name

1
2
3
module MyModule;

endmodule
MyModule

Hierarchy Child Creation

A Hierarchy with one Module is for sure rather useless. As soon as we have defined two Modules, we can start creating a Hierarchy.

A Hierarchy is composed by creating an Instance of a Module inside the definition of another module. Let’s start by defining a second module:

1
2
3
4
5
6
7
module MyModule;

endmodule

module MyChildModule;

endmodule
../../_images/twomodules.png

Now we can compose the hierarchy:

1
2
3
4
5
6
7
8
9
module MyChildModule;

endmodule

module MyModule;

   MyChildModule instanceA();

endmodule
../../_images/twomodules-composed.png

It is also naturally possible to replicate the same Module instance multiple times:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
module MyChildModule;

endmodule

module MyModule;

   MyChildModule instanceA();

   MyChildModule instanceB();

   MyChildModule instanceC();
endmodule
../../_images/twomodules-3instances.png

Signals: Input / Output

In verilog, wires, registers, input and outputs are called signals.

A formal definition of a signal syntax is:

TYPE SIZE NAME ARRAYSIZE? ; // ARRAYSIZE is rarely used

We will see later what type can be.

I/O Definition

Next, we have defined that components of a System are linked by input and outputs.

Input and Outputs are naturally related to the Hierarchy definition, i.e the Module definition.

1
2
3
4
module MyModule (

   input clock
);
../../_images/io-input.png

The module MyModule has one input, a clock. The clock is usually the first input one writes on a module definition.

A format definition for an IO format can be:

(input|output) SIGNALTYPE? SIZE NAME;

I/O Width

In Verilog, a Signal (i.e an Input/Output, a wire, a register etc…) can have a bus width.

The following figures shows the I/O definition for a simple counter, outputing an 8 bits value

1
2
3
4
5
6
7
8
9
module Counter (

   input clock,

   // Note the Bus definition before the name
   output [7:0] value
);

endmodule
../../_images/io-counter.png

The formal definition of Size definition is:

TYPE? SIGNALTYPE [ MSB : LSB ] NAME
  • TYPE is input / output , or nothing for local signals
  • SIGNALTYPE is for example wire or reg
  • MSB and LSB define the size of the bus: [7:0] for 8 bits, [7:2] for 6 bits etc…

I/O Connection

We have seen before how to compose a hiearchy.

Now we need to see how to connect input and output.

The following simple example shows how to propagate the clock on MyModule down to a Counter instance, and the value of the counter to the output of MyModule.

Note that the picture uses the “Container” representation of the hiearchy, in comparison to the UML-like composition diagram.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module Counter (

   input clock,

   // Note the Bus definition before the name
   output [7:0] value
);

endmodule

// MyModule has to duplication the IO of Counter
module MyModule (
   input clockForCounter,
   output [7:0] valueForCounter
);

   Counter mycounter (

      .clock(clockForCounter),

      .value(valueForCounter)
   );

endmodule
../../_images/io-connection.png

the format definitoin for the IO connection is:

. IONAME ( CONNECTION ) ,? // “,” only after the last connection

Pipeline Stage Definition

Clock Synchronous Block

As defined previously, our design will be clock synchronous, that means that register values holding the current logic function results are updated on a clock edge.

In other words, a set of logic function can be described as “reacting” to a clock edge.

This construction is modeled in verilog using an “always” block, to which a sensitivity list can be passed

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
module Counter (

   input clock,

   // Note the "reg" definition to value
   output reg [7:0] value
);

   // "begin" and "end" are like "{ ... }" in C
   //  value is updated after the
   always @(posedge clock) begin

      value <= value +1;

   end

endmodule

On this example, note the output definition change:

  • “output [7:0] value” was turned into “output reg [7:0] value”, because value is now a memory holding the counter state.

Register/Non Register value update

In the previous example, a value output reg is updated, using the <= operator.

This operator is called non blocking assignment, as opposed to the blocking assigment.

One of both has to be used when assigning the result of a logic function to a value:

Non Blocking assignment

LEFT <= EXPRESSION;
// At this point,  LEFT does NOT hold EXPRESSION yet

A Non blocking assigment means, that the LEFT side of the assigment will be updated with the right side at a later point, but not right now.

This may seem strange, but we have to remember two essential points again:

  • The code does NOT represent a sequential set of instructions. The lines of code don’t represent values that are immediately valid.
  • In a pipeline stage, register values are valid first after the next clock edge.

Knowing this, a non blocking assignment simply means that we are updating a register with a value, which will be visible after the next clock cycle.

A very simple example for example:

  • input2 has a defined value of 1
  • b depends on a , a on input1 and input2, input1 depends on b
  • b and a are initially 0
a <= input1 ^ input2;  // XOR
b <= a ^ input2; // XOR
input1 <= ! b
Value Clock 1 Clock 2 Clock 3 Clock 4 Clock 5
input1 0 1 1 1 0
input2 1 1 1 1 1
a 0 1 0 1 0
b 0 0 0 1 0

Using this table, you can see that each clock cycle value depends on the values of the previous clock.

Blocking assignment

LEFT = EXPRESSION;
// At this point,  LEFT DOES hold EXPRESSION

On the contrary, a blocking assignment enforces the next line of code to consider the right side EXPRESSION as the actual value for the LEFT.

Once again, we are not in a software environment, so it does not mean the value is saved somehow.

It only means that the LEFT value represents the expression on the right.

As a consequence, every use of = adds up the logic of expression to the logic function, until a <= non blocking assignement is encountered to save the cumulative logic in a register.

a = 1;
b = 1;
z = 2;

c <= a^b
d <= b | c

e <= c & z
Register Cummulative logic
c a ^ b
d b | (a ^ b)
e (a ^ b) & z

With the table, it is then clear that c is a smaller function than d and e.

Tip

Use blocking assignments to “save” a logic function on some bits which is reused for example to conditionally update different registers.

Simple Enable example

To quickly illustrate blocking/non-blocking, here is an example of an enable signal.

Enable signals are very important as they allow hardware optimisations during the synthesis phase.

// Blocking assignment
// Enable condition is a static logic function based on two inputs
wire enable =  input1 ^ input2;  // XOR

always @(posedge clk) begin

   // Only increment the output value if the enabling input function is 1 (true)
   if (enable) begin

      output <= output + 1;
   end

end

Reset

Most registers in your design will need a clear initial value.

If you don’t define a reset value, your registers will have a value of 0 or 1 in real life, or “undefined” in the simulation.

This case is pretty bad for example if you are generating a start/stop signal. When your design starts, you will want it to be stopped, then start when required by the general system behaviour.

There are two type of reset signals:

  • Synchronous: The reset is a condition in the synchronous logic, is added to the main logic and updates the values on the clock edge
  • Asynchronous: The reset is not dependend on the clock, and will be set to the Set or Reset inputs of the Flip-Flops.

This subject will be studies during the lab work and the Technology mapping lecture, but here is how to implement them:

  • Resets are in a Synchronous Pipeline Always Block, because they define the behavior of the pipeline stage
  • For synchronous, implement the reset as an if condition, just like standard logic.
  • For asynchronous, add an activation signal to the always sensitivity list.
    • Indeed, the always block is active for both clock, or the reset when it comes.

Synchronous reset

module MyModule (
   input clk,
   input reset
);

// Synchronous Reset
always @(posedge clk) begin

   // Reset case if it is 1
   if (reset) begin
      output <= 0;
   end
   else begin
      output <= output + 1;
   end

end

endmodule

Asynchronous reset

module MyModule (
   input clk,
   input reset
);

// ASynchronous Reset -> react when reset becomes 1 -> posedge
always @(posedge clk or posedge reset) begin

   // Reset case if it is 1, code is the same
   if (reset) begin
      output <= 0;
   end
   else begin
      output <= output + 1;
   end

end

endmodule

Parameterizing a Module

In many cases, the functionality of a design can be made independent of some parameters.

The width of data buses is the most common use case.

As an example, we can look at an hypothetical addressable memory module, which could used to save data, like a RAM memory would. It is easy to understand that the width of the data bus will depend on the application, and the width of the address bus on the size of the memory.

Rewriting the module for each needed Width and Size would be difficult, so it is possible to parameterized these quantities and define them when instantiating the module.

Parameter Definition

For example, a memory module with a data width of 8 and address width of 16, will define these sizes in an abstract manner, using a NAME which will be replaced by the chosen value at instantiation:

../../_images/memory-fixed.png

Input/Output are fixed

../../_images/memory-parameter.png

Input/Output are defined by a name

Now the according Verilog description would be, on the left with no parameters, on the right with parameters:

module MemoryFixed (

   input wire [7:0] data_in,
   input wire [15:0] address,

   output wire [7:0] data_out,
);

...

endmodule
// Parameter declaration in "#(...)" part
module MemoryParameter #( parameter WIDTH = 8, parameter AWIDTH = 16 ) (

   input wire [WIDTH - 1:0] data_in,
   input wire [AWIDTH - 1:0] address,

   output wire [WIDTH - 1:0] data_out,
);

...

endmodule

The formal definition of a parameter list on a module is thus:

module NAME #(parameter PARAMETERNAME = DEFAULTVALUE , parameter PARAMETERNAME = DEFAULTVALUE …)

Now let’s look at how these parameters can be defined when instantiating a module.

Parameter Instantiation

When instantiating a module, the parameters can be overwritten and the logic will behave as if the parameter name was its value written statically.

For example, we can instantiate the memory twice with different width and size:

../../_images/memory-instances.png
module testbench;

   MemoryParameter #(.WIDTH(24),.AWIDTH(18)) memory1 (

        // Connections
   );

   MemoryParameter #(.WIDTH(64),.AWIDTH(32)) memory2 (

        // Connections
   );

endmodule

Value Assignment Syntaxes

In this section we will present the syntax elements to assign values.

Constant Value Representation: Hex, bit, binary

The format of a constant value can be chosen to easily represent the assigned bits.

The formal definition of the value format is:

SIZE FORMAT VALUE

where FORMAT can be:

  • h for hexadecimal
  • b for bits
  • d for decimal
  • o for octal

If no format is defined, the decimal type of integer (32bits) is assumed.

A few examples are sufficient to understand the syntax:

// 16 bits in hex
value <= 16'hABCD;

// 16 bits in bits
// _ can be used to separate the characters in value, good for readability
value <= 16'b0000_0000_0000_0000;

// Assumed 32bit integer
value <= 1024;

// Error: Format SIZE does not match VALUE size
value <= 16'hAB; // only 8 bits give
value <= 16'hABCDEF // 24 bits defined for 16 bit size

Value to value asssignment

A value can be assigned from another value.

If the sizes differ, bits can be selected with the array operator used to define sizes:

reg [7:0]  value; // 8 bits result
reg [23:0] sourceValue; // 24 bits
reg [7:0]  sourceValue8bits; // 8 bits
...


value <= sourceValue[7:0]; // Lower 8 bits used

value <= sourceValue[15:8]; // Some other bits

value <= sourceValue8bits; // Same size

Segment and Bus Assignment: Aggregation

The bits of a value can be aggregated from different values:

reg [23:0]  value; // 24 bits result

reg [15:0] sourceValue; // 16 bits
reg [7:0]  sourceValue8bits; // 8 bits



value <= { sourceValue , sourceValue8bits }; // MSBs are sourceValue

value <= { sourceValue8bits, sourceValue }; // MSBs are sourceValue8bits

// sourceValue8bits in the middle of sourceValue
value <= { sourceValue[15:8], sourceValue, sourceValue[7:0]}; // MSBs are sourceValue8bits

Constant to Variable Size Assignment

As we have seen with Parameters, the size of a bus can be defined by a name.

This is problematic when assigning a constant value to a variable size bus, like in a reset block.

The assignment syntax supports a “duplication” option:

{ COUNT {VALUE} }

This will duplicate VALUE , COUNT times.

Example:

module mymodule #(SIZE = 24) (

   ...

   // Maybe in reset/reinit block ?
   data <= { SIZE {1'b0} }; // All zeros

   // Combine with aggregation
   data <= { (SIZE-4) {1'b1}, 4 {1'b0} }; // 4 LSB to zero, rest of MSB to 1


endmodule

Simulation Testbench

A simulation testbench is used to verify a system, as was introduced previously.

A digital system to be tested is usually called Device under test

We will often repeat that a Simulation is NOT a hardware description, however, it is possible to use the Hardware Description Language to write the simulation software.

This way the user does not have to learn two technologies or write a complex system, like a Verilog Design and a C testing software.

What is a testbench?

A testbench can be described first with a few simple criterias:

  • It is a top level hierarchy, which means it is the parent container module, and the tested designs are children
  • It is a software and does not represent a physical entity, so it has no input and outputs.
  • It will drive some signals like registers using the same syntax as in a standard module.
  • These signals are connected to the instantiated children to activate their logic during simulation.
  • It does not really matter how the signal values are generated, as it won’t be turned into a hardware. The signals just need to make sense.

Example: Device Under Test

From the preivously defined list, we can very easily write the code:

Hint

Top level hiearchy and Device Under Test == Verilog Module, one child instance, no Input outputs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// MyModule is the Device Under Test
module mymodule (
   input clock
);

endmodule

// Test bench No Input/Outputs
module mymodule_testbench;

   // DUT instance
   mymodule dut () ;

endmodule
../../_images/testbench-1.png

Signal Driving: Clock example

The testbench now has to drive some signals. Driving a signal means:

  • Declaring an assignable value, like “reg” or “logic”.
  • Setting an initial value at the beginning of the simulation
  • Write the testbench programm to drive the signal according to the tested design specification

We can show here how to create a clock signal, because it is the first thing all designs will need. In this example we used the “reg”, “initial” and “always” constructs, the details are in the code sample:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
module mymodule_testbench;

   // Declare signal value
   reg clk;

   // Set a value at beginning of simulation
   // "initial" is executed at the beginning of times
   initial begin

      clk = 0; // 0 Value

      // Finish simulation after a while
      #200 $finish();
   end

   // Use an always block without condition
   // "always" without condition will run all the time like an infinite loop
   always begin

      // Every "10", invert the clock value
      // "#" is used to wait a certain simulation time
      // without #10 , this would never way to execute over and over agains
      // it would be like an infinite loop,
      // preventing the simulation from going to the next simulation time event.
      #10 clk <= ~ clk;
   end

   // DUT instance
   // Connect the clock input to the local "clk" register
   mymodule dut (

      .clock(clk)

   ) ;

endmodule
../../_images/testbench-clock.png

Signal Driving Sequence: Reset example

The Clock example shows how to create a regular toggling signal.

However, control signals of a design, like “enable”, “reset”, “stop”, have to be sequenced.

It means they change based on a specific use case, which can be written down in textual form, for example:

  • if “stop” is high, the outputs don’t change anymore.
  • if “reset” is high, the outputs become “0”.

To illustrate how to sequence a signal, we will take a reset signal as an example, as it is also with the clock, the first signal which is added to a test bench.

To do so:

  • Declare an assignable value for the register
  • Set an initial value, like “1” if the reset is high active, or low active
  • After a waiting time (use of the # operator), change the value of the reset to inactive.
  • During this time, the clock will run and let the device under test be reseted if it is using a synchronous reset.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
module mymodule_testbench;

   // Clock and reset
   reg clk;
   reg res;

   // Clock generator
   always begin
      #10 clk <= ~ clk;
   end

   // Begining of times
   initial begin

      clk = 0;
      res = 1; // Active reset

      // Wait 200 and deactivate reset
      // Wait enough time here to allow the clock to
      // toggle a few times at least
      #200 res = 0;

      #200 $finish();
   end

   // DUT instance
   // Connect the clock input to the local "clk" register
   mymodule dut (

      .clock(clk),
      .reset(res)
   ) ;

endmodule
../../_images/testbench-reset.png

Undefined values

In a simulation, all the register values which have not be initialised at some point by the design are marked undefined.

../../_images/sim-undefined.png

Red values are undefined.

If you encounter an undefined value in the simulation, look first where it is supposed to be driven, then check why it is red:

  • In the testbench -> you forgot to set a value in the main “initial” block.
  • In the design -> the initial assignment has never been reached:
    • You problably need a reset
    • If there is a reset, you may have forgotten to initialise the signal
    • Maybe the testbench is not testing all the use cases, and the one leading to the first assigment was never implemented.

Signal Synchronisation

When writing a testbench, you don’t want to always have to look for a precise time to wait based on the clock generator and use # to change values at the right moment.

Instead, you can synchronise on the edge of a signal or wait for a certain value quite easily:

  • @(posedge SIGNAL) : Wait for a signal edge
  • wait(CONDITION) : Wait for a specific condition to be valid

Example:

initial
begin

   ...

   // Wait 200 then align on a clock edge to change data
   // at time which makes sense
   #200 @(negedge clk);

   // Wait for a certain signal to be 1
   wait(finished == 1);

end